import os
import requests
import pandas as pd
from tqdm import tqdm
import concurrent.futures
import time
import urllib.parse
from pathlib import Path

def download_file(url, save_path, headers=None):
    """
    从URL下载文件并保存到指定路径，显示下载进度条
    :param url: 文件的URL
    :param save_path: 保存路径
    :param headers: 请求头
    :return: 是否下载成功
    """
    if not url:
        print(f"下载URL为空，无法下载")
        return False
    
    # 确保保存文件的目录存在
    os.makedirs(os.path.dirname(os.path.abspath(save_path)), exist_ok=True)
    
    # 设置默认请求头
    if headers is None:
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate, br',
            'Connection': 'keep-alive'
        }
    
    try:
        # 发送GET请求，启用流模式
        response = requests.get(url, headers=headers, stream=True, timeout=30, verify=True)
        
        # 检查请求是否成功
        if response.status_code == 200:
            # 获取总文件大小（如果存在）
            total_size = int(response.headers.get('content-length', 0))
            
            # 使用 tqdm 显示进度条
            with open(save_path, 'wb') as file, tqdm(
                    desc=os.path.basename(save_path),
                    total=total_size,
                    unit='iB',
                    unit_scale=True,
                    unit_divisor=1024,
            ) as bar:
                for data in response.iter_content(chunk_size=8192):  # 使用较大的块大小提高效率
                    if data:
                        size = file.write(data)
                        bar.update(size)
            
            # 验证下载的文件大小
            downloaded_size = os.path.getsize(save_path)
            if downloaded_size == 0:
                print(f"警告: 下载的文件 '{os.path.basename(save_path)}' 大小为0字节")
                return False
                
            print(f"文件已成功下载并保存到 {save_path}，大小: {downloaded_size/1024/1024:.2f} MB")
            return True
        else:
            print(f"请求失败，状态码：{response.status_code}")
            return False
    except Exception as e:
        print(f"下载过程中发生错误：{e}")
        return False

def get_filename_from_url(url):
    """
    从URL中提取文件名
    :param url: 文件URL
    :return: 文件名
    """
    try:
        # 解析URL并获取路径部分
        parsed_url = urllib.parse.urlparse(url)
        path = parsed_url.path
        
        # 从路径中提取文件名
        filename = os.path.basename(path)
        
        # 如果文件名为空或没有扩展名，则使用默认名称
        if not filename or '.' not in filename:
            # 尝试从查询参数中获取文件名
            query_params = urllib.parse.parse_qs(parsed_url.query)
            if 'fsname' in query_params:
                filename = query_params['fsname'][0]
            else:
                # 使用URL的MD5哈希作为文件名
                import hashlib
                filename = f"download_{hashlib.md5(url.encode()).hexdigest()[:8]}"
        
        return filename
    except Exception as e:
        print(f"从URL提取文件名时出错: {e}")
        # 返回一个基于时间戳的默认文件名
        return f"download_{int(time.time())}"

def process_url(row, download_dir, column_name='url', filename_column=None):
    """
    处理Excel行中的URL并下载文件
    :param row: Excel的一行数据
    :param download_dir: 下载目录
    :param column_name: 包含URL的列名
    :param filename_column: 包含文件名的列名（如果有）
    :return: 是否下载成功
    """
    try:
        # 获取URL
        url = row[column_name]
        if not isinstance(url, str) or not url.strip():
            print("URL为空或无效")
            return False
        
        # 确定文件名
        if filename_column and filename_column in row and row[filename_column]:
            # 使用Excel中指定的文件名
            filename = str(row[filename_column])
        else:
            # 从URL中提取文件名
            filename = get_filename_from_url(url)
        
        # 构建保存路径
        save_path = os.path.join(download_dir, filename)
        
        # 下载文件
        print(f"开始下载: {url} -> {filename}")
        return download_file(url, save_path)
    except Exception as e:
        print(f"处理URL时发生错误: {e}")
        return False

def main():
    # 配置参数 - 使用硬编码的值
    excel_file = "F:\\apk20250917\\url.xlsx"
    url_column = "url"
    filename_column = None  # 不使用文件名列
    download_dir = "F:\\apk20250917\\downloads"
    max_workers = 5  # 并发下载数量
    
    # 确保下载目录存在
    os.makedirs(download_dir, exist_ok=True)
    
    if not os.path.exists(excel_file):
        print(f"错误: 文件 '{excel_file}' 不存在")
        return
    
    try:
        # 读取Excel文件
        print(f"正在读取Excel文件: {excel_file}")
        df = pd.read_excel(excel_file)
        
        # 检查URL列是否存在
        if url_column not in df.columns:
            print(f"错误: Excel文件中不存在列 '{url_column}'")
            return
        
        # 过滤掉URL为空的行
        df = df.dropna(subset=[url_column])
        total_urls = len(df)
        
        if total_urls == 0:
            print("没有找到有效的URL")
            return
        
        print(f"找到 {total_urls} 个URL，开始下载...")
        
        # 使用线程池并发下载
        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
            # 提交所有下载任务
            futures = []
            for i, row in df.iterrows():
                future = executor.submit(process_url, row, download_dir, url_column, filename_column)
                futures.append(future)
            
            # 处理完成的任务
            completed = 0
            failed = 0
            
            for future in tqdm(
                concurrent.futures.as_completed(futures),
                total=len(futures),
                desc="总体进度",
                unit="文件"
            ):
                try:
                    success = future.result()
                    if success:
                        completed += 1
                    else:
                        failed += 1
                except Exception as e:
                    print(f"处理任务时发生异常: {e}")
                    failed += 1
        
        print(f"\n下载完成: 成功 {completed} 个, 失败 {failed} 个")
    
    except Exception as e:
        print(f"程序执行过程中发生错误: {e}")

if __name__ == "__main__":
    start_time = time.time()
    main()
    elapsed_time = time.time() - start_time
    print(f"总耗时: {elapsed_time:.2f} 秒")
